Skip to content

Add webhooks feature to Insight dashboard #6929

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

AmineAfia
Copy link
Contributor

@AmineAfia AmineAfia commented May 2, 2025

Add Webhooks Feature to Insight Dashboard

This PR adds a new webhooks feature to the Insight dashboard, allowing users to create, test, and manage webhooks for blockchain events and transactions. The implementation includes:

  • New API client for webhook operations (create, get, delete, test)
  • Dedicated webhooks page with a table to display and manage existing webhooks
  • Multi-step modal for creating new webhooks with:
    • Basic information configuration
    • Filter options for events or transactions
    • ABI fetching and processing for smart contracts
    • Test functionality to verify webhook endpoints

The PR also includes several UI improvements:

  • Added a key to sidebar separator to fix a React warning
  • Fixed props spreading in multi-select component to avoid DOM validation errors
  • Created a reusable layout for the Insight section with proper navigation tabs

The webhooks feature enables users to receive real-time notifications about on-chain events through their own endpoints, supporting both event and transaction monitoring with customizable filters.


PR-Codex overview

This PR introduces a comprehensive update to the insight module, enhancing the webhook functionality with new components and improved layout, while refining user interactions and error handling.

Detailed summary

  • Enhanced Layout component for project and team insights.
  • Added StepIndicator and BasicInfoStep for webhook creation.
  • Implemented FilterDetailsStep for advanced webhook settings.
  • Improved ReviewStep for webhook configuration review.
  • Introduced CreateWebhookModal for creating new webhooks.
  • Added client-side testing for webhooks with useTestWebhook hook.
  • Updated API interactions in webhooks.ts for better error handling.
  • Refined UI components for better user experience in the webhook management interface.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Demo

CleanShot 2025-05-07 at 18.55.41.mp4 (uploaded via Graphite)

Copy link

changeset-bot bot commented May 2, 2025

⚠️ No Changeset found

Latest commit: 5d068e0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented May 2, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
thirdweb-www ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 16, 2025 6:39pm
4 Skipped Deployments
Name Status Preview Comments Updated (UTC)
docs-v2 ⬜️ Skipped (Inspect) May 16, 2025 6:39pm
login ⬜️ Skipped (Inspect) May 16, 2025 6:39pm
thirdweb_playground ⬜️ Skipped (Inspect) May 16, 2025 6:39pm
wallet-ui ⬜️ Skipped (Inspect) May 16, 2025 6:39pm

@vercel vercel bot temporarily deployed to Preview – login May 2, 2025 19:15 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui May 2, 2025 19:15 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 May 2, 2025 19:15 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground May 2, 2025 19:15 Inactive
@github-actions github-actions bot added the Dashboard Involves changes to the Dashboard. label May 2, 2025
Copy link
Contributor Author


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@AmineAfia AmineAfia marked this pull request as ready for review May 2, 2025 19:15
@AmineAfia AmineAfia requested review from a team as code owners May 2, 2025 19:15
Copy link

codecov bot commented May 2, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 55.49%. Comparing base (8f605d9) to head (5d068e0).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6929   +/-   ##
=======================================
  Coverage   55.49%   55.49%           
=======================================
  Files         909      909           
  Lines       58406    58406           
  Branches     4069     4069           
=======================================
  Hits        32415    32415           
  Misses      25886    25886           
  Partials      105      105           
Flag Coverage Δ
packages 55.49% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

github-actions bot commented May 2, 2025

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
thirdweb (esm) 54.18 KB (0%) 1.1 s (0%) 204 ms (+189.33% 🔺) 1.3 s
thirdweb (cjs) 194.07 KB (0%) 3.9 s (0%) 391 ms (+45.75% 🔺) 4.3 s
thirdweb (minimal + tree-shaking) 5.68 KB (0%) 114 ms (0%) 66 ms (+976.8% 🔺) 180 ms
thirdweb/chains (tree-shaking) 524 B (0%) 11 ms (0%) 38 ms (+2431.09% 🔺) 48 ms
thirdweb/react (minimal + tree-shaking) 19.53 KB (0%) 391 ms (0%) 91 ms (+702.67% 🔺) 481 ms

@AmineAfia AmineAfia force-pushed the Add_webhooks_feature_to_Insight_dashboard branch from 8887def to 4b39e26 Compare May 2, 2025 19:20
@vercel vercel bot temporarily deployed to Preview – wallet-ui May 2, 2025 19:20 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground May 2, 2025 19:20 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 May 2, 2025 19:20 Inactive
@vercel vercel bot temporarily deployed to Preview – login May 2, 2025 19:20 Inactive
@AmineAfia AmineAfia force-pushed the Add_webhooks_feature_to_Insight_dashboard branch from 4b39e26 to b083bcc Compare May 2, 2025 21:29
@vercel vercel bot temporarily deployed to Preview – login May 2, 2025 21:29 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui May 2, 2025 21:29 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 May 2, 2025 21:29 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground May 2, 2025 21:30 Inactive
@AmineAfia AmineAfia force-pushed the Add_webhooks_feature_to_Insight_dashboard branch from b083bcc to 36fcc86 Compare May 3, 2025 12:50
Comment on lines +39 to +42
const handleTestWebhook = async () => {
if (webhookUrl) {
await testWebhookEndpoint(webhookUrl);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The testWebhookEndpoint function requires a type parameter that's missing in this call. This will cause webhook tests to fail since the API needs to know whether it's an 'event' or 'transaction' type webhook.

Consider updating the function call to include the current filter type from the form:

await testWebhookEndpoint(webhookUrl, form.watch('filterType'));

This ensures the correct webhook type is passed to the testing endpoint.

Suggested change
const handleTestWebhook = async () => {
if (webhookUrl) {
await testWebhookEndpoint(webhookUrl);
}
const handleTestWebhook = async () => {
if (webhookUrl) {
await testWebhookEndpoint(webhookUrl, form.watch('filterType'));
}

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +31 to +36
toAddresses: z
.string()
.nonempty({ message: "To address is required" })
.refine((val) => val.split(",").every(isValidAddress), {
message: "Enter a valid address",
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation schema has an inconsistency between addresses and toAddresses fields. Both are marked as required with .nonempty(), but the UI in FilterDetailsStep only shows one field at a time based on the filterType.

When filterType is "transaction", the form will still attempt to validate the hidden addresses field, causing validation errors for users. Similarly, when filterType is "event", it will validate the hidden toAddresses field.

Consider implementing conditional validation based on the filterType:

export const webhookFormSchema = z.discriminatedUnion('filterType', [
  z.object({
    filterType: z.literal('event'),
    addresses: z.string().nonempty().refine(/*...*/),
    // Make toAddresses optional for event type
    toAddresses: z.string().optional(),
    // other fields...
  }),
  z.object({
    filterType: z.literal('transaction'),
    // Make addresses optional for transaction type
    addresses: z.string().optional(),
    toAddresses: z.string().nonempty().refine(/*...*/),
    // other fields...
  })
]);

This approach ensures fields are only validated when they're relevant to the selected filter type.

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

@AmineAfia AmineAfia force-pushed the Add_webhooks_feature_to_Insight_dashboard branch from 5a6b34f to b7f5b7f Compare May 13, 2025 00:21
@vercel vercel bot temporarily deployed to Preview – docs-v2 May 13, 2025 00:21 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground May 13, 2025 00:21 Inactive
@vercel vercel bot temporarily deployed to Preview – login May 13, 2025 00:21 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui May 13, 2025 00:21 Inactive
Comment on lines +32 to +34
} catch (error) {
console.error("Error loading project or webhooks", error);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling here only logs to the console without providing user feedback. Consider implementing a proper error state UI component that distinguishes between "no webhooks exist" and "failed to load webhooks." This would improve the user experience by clearly communicating when there's a system issue rather than showing an empty state that incorrectly suggests the user has no webhooks configured.

Suggested change
} catch (error) {
console.error("Error loading project or webhooks", error);
}
} catch (error) {
console.error("Error loading project or webhooks", error);
setError("Failed to load webhooks. Please try again later.");
}

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

Comment on lines 73 to 76
export interface AbiResponse {
abis: Record<string, AbiData>;
errors: Record<string, string>;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exported interface 'AbiResponse' is not being used anywhere in the codebase, which is causing the knip linter to fail. Either remove the 'export' keyword to make it a local interface, or remove the interface entirely if it's not needed.

Spotted by Diamond (based on CI logs)

Is this helpful? React 👍 or 👎 to let us know.

@AmineAfia AmineAfia force-pushed the Add_webhooks_feature_to_Insight_dashboard branch from b7f5b7f to 6e90e96 Compare May 13, 2025 00:26
@vercel vercel bot temporarily deployed to Preview – wallet-ui May 13, 2025 00:26 Inactive
@vercel vercel bot temporarily deployed to Preview – login May 13, 2025 00:26 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground May 13, 2025 00:26 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 May 13, 2025 00:26 Inactive
Comment on lines +22 to +24
.refine((val) => val.split(",").every(isValidAddress), {
message: "Enter a valid address",
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current address validation has a potential issue with empty inputs. The .refine() method will pass validation for strings containing only commas or spaces (like ",,,") because it's checking if every address is valid, but not verifying that valid addresses actually exist.

Consider enhancing the validation to ensure there's at least one valid address after splitting and filtering:

.refine(
  (val) => {
    const addresses = val.split(',').map(addr => addr.trim()).filter(Boolean);
    return addresses.length > 0 && addresses.every(isValidAddress);
  }, 
  { message: 'Enter at least one valid address' }
)

This approach first trims and filters out empty entries, then verifies both that there's at least one address and that all addresses are valid.

Suggested change
.refine((val) => val.split(",").every(isValidAddress), {
message: "Enter a valid address",
}),
.refine(
(val) => {
const addresses = val.split(",").map(addr => addr.trim()).filter(Boolean);
return addresses.length > 0 && addresses.every(isValidAddress);
},
{ message: "Enter at least one valid address" }
),

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

@@ -165,7 +165,9 @@ export const MultiSelect = forwardRef<HTMLButtonElement, MultiSelectProps>(
{props.customTrigger || (
<Button
ref={ref}
{...props}
{...(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this added?


{/* Navigation Buttons */}
<div className="flex justify-between pt-4">
{currentStep !== WebhookFormSteps.BasicInfo ? (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The navigation buttons should also go inside the step component so this is easier to maintain and update later

render={({ field }) => (
<FormItem className="flex flex-col">
<div
className={cn(
Copy link
Member

@MananTank MananTank May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove cn here - not required, please also remove this from everywhere else as well.

only use it when conditional styles need to be added or when the className string is too large and you want to break it down to multiple lines

}));
}, [pairs, isOpen, addresses, chainIds, type, thirdwebClient]);

const results = useQueries({ queries });
Copy link
Member

@MananTank MananTank May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using useQueries - If the number of queries can be large here it will crash the page due to a large amount of memory used. If possible, please do all request in a single useQuery

* @param address Address to validate
* @returns Boolean indicating if address is valid
*/
export const isValidAddress = (address: string): boolean => {
Copy link
Member

@MananTank MananTank May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a isAddress utility exported from thirdweb/utils fro this already


if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to delete webhook: ${errorText}`);
Copy link
Member

@MananTank MananTank May 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't throw errors in server actions - You won't be able to see them in client component (you will only be able to see errors in local dev server but not in vercel preview or production environments)

If you want to return error message, you have to return the error message manually

Example:

return {
  errorMessage: await response.text()
}

@AmineAfia AmineAfia force-pushed the Add_webhooks_feature_to_Insight_dashboard branch from 6e90e96 to 5d068e0 Compare May 16, 2025 18:32
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground May 16, 2025 18:32 Inactive
@vercel vercel bot temporarily deployed to Preview – login May 16, 2025 18:32 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 May 16, 2025 18:32 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui May 16, 2025 18:32 Inactive
Comment on lines +31 to +36
toAddresses: z
.string()
.nonempty({ message: "To address is required" })
.refine((val) => val.split(",").every(isValidAddress), {
message: "Enter a valid address",
}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation schema for toAddresses currently requires this field unconditionally, but it should only be required when filterType is set to 'transaction'. For event webhooks, this field isn't applicable. Consider implementing conditional validation using Zod's .superRefine() or a dynamic schema based on the selected filter type to prevent validation errors when users are creating event webhooks. This would improve the form experience by only validating fields relevant to the current webhook type.

Suggested change
toAddresses: z
.string()
.nonempty({ message: "To address is required" })
.refine((val) => val.split(",").every(isValidAddress), {
message: "Enter a valid address",
}),
toAddresses: z
.string()
.optional()
.superRefine((val, ctx) => {
// Only validate if filterType is 'transaction'
if (ctx.parent.filterType === 'transaction') {
if (!val || val.trim() === '') {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "To address is required for transaction webhooks",
});
return;
}
if (!val.split(",").every(isValidAddress)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Enter a valid address",
});
}
}
}),

Spotted by Diamond

Is this helpful? React 👍 or 👎 to let us know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Dashboard Involves changes to the Dashboard.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants